home *** CD-ROM | disk | FTP | other *** search
/ MacWorld UK 2000 March / MW_UK_2000_03.iso / Shareware World / Utilities / Text Processing / Alpha / Tcl / SystemCode / indentation.tcl < prev    next >
Encoding:
Text File  |  1999-11-08  |  17.1 KB  |  592 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #    Vince's    Additions -    an extension package for Alpha
  4.  # 
  5.  #    FILE: "indentation.tcl"
  6.  #                      created: 27/7/97 {1:08:08 am}    
  7.  #                     last update: 11/08/1999 {10:35:08 AM}    
  8.  #    Author:    Vince Darley
  9.  #    E-mail:    <vince@santafe.edu>
  10.  #      mail:    317 Paseo de Peralta, Santa Fe, NM 87501, USA
  11.  #       www:    <http://www.santafe.edu/~vince/>
  12.  #    
  13.  # ###################################################################
  14.  ##
  15.  
  16. alpha::flag electricBraces 0.1 {global C C++ Java Tcl Perl} help {
  17.     Enabling the 'Electric Braces' feature tells Alpha to treat the 
  18.     left or right brace '{', '}' keys as special keypresses which 
  19.     enter the '{' or '}' character, followed by a return and then 
  20.     indent the following line correctly.  It is useful for those 
  21.     programming modes in which '{' and '}' are used to delineate 
  22.     blocks of code in 'for' loops or 'if-then-else' groups etc.
  23. }
  24.  
  25. alpha::flag electricSemicolon 0.1 {global C C++ Java Perl} help {
  26.     Enabling the 'Electric Semicolon' feature tells Alpha to treat the 
  27.     semicolon key ';' as special keypresses which enters the ';' 
  28.     character followed by a return and then indents the following line 
  29.     correctly.  It is useful for some programming modes in which ';' 
  30.     normally ends a line.
  31.     
  32.     The ';' key is context-dependent so you can still enter a 
  33.     for( ; ; ) loop in C mode (for instace) without Alpha messing 
  34.     things up.
  35. }
  36.  
  37. alpha::feature electricReturn 0.1 {global} {
  38.     if {[info tclversion] >= 8.0} {
  39.     linkVar indentOnReturn
  40.     }
  41.     set indentOnReturn 0
  42. } {set indentOnReturn 1} {set indentOnReturn 0} help {
  43.     Enabling the 'Electric Return' feature tells Alpha to indent the 
  44.     following line automatically whenever you press return.
  45. }
  46.  
  47. alpha::flag electricColon 0.1 {global} help {
  48.     Enabling the 'Electric Colon' feature tells Alpha to carry out a 
  49.     special action when the user presses colon.
  50. }
  51.  
  52. alpha::flag autoContinueComment 0.1 {global} help {
  53.     Enabling the 'autoContinueComment' feature tells Alpha to check when
  54.     the users hits return whether the current line is a comment, and if
  55.     so, to indent and insert comment characters so that the following
  56.     line continues the comment.
  57. }
  58.  
  59. alpha::flag indentUsingSpacesOnly 0.1 {global TeX} help {
  60.     If set, do not use tabs to indent, but spaces only.  This is mostly
  61.     useful for modes in which the 'tab' character has a special meaning,
  62.     such as python or TeX (the latter usually only for TeX as a programming
  63.     language, not as a document preparation system).
  64. }
  65.  
  66. alpha::flag commentsArentSpecialWhenIndenting 0.1 {global TeX} help {
  67.     Indent lines to level of previous line if set, otherwise to level 
  68.     of previous non-comment line (in which case Alpha will search 
  69.     backwards for some distance).  If you're in the habit of indenting 
  70.     your comments to the same level as your code, this setting 
  71.     shouldn't matter (and setting it is slightly more efficient).
  72.     
  73.     One case in which it can be _much_ more efficient is when your 
  74.     files contain vast comments (especially .dtx files in TeX mode, 
  75.     for instance).  For these files, you should activate this feature.
  76. }
  77.  
  78. namespace eval indent {}
  79. namespace eval Bind {}
  80. namespace eval text {}
  81.  
  82. proc IndentLine {} { bind::IndentLine }
  83.  
  84. proc typeText {t} {
  85.     if {[isSelection]} {
  86.     deleteSelection
  87.     }
  88.     insertText $t
  89. }
  90.  
  91. proc normalLeftBrace {} {
  92.     typeText "\{"
  93. }
  94. proc normalRightBrace {} {
  95.     typeText "\}"
  96.     blink [matchIt "\}" [pos::math [getPos] - 2]]
  97. }
  98.             
  99. proc literalChar {} {
  100.     return [expr {[lookAt [pos::math [getPos] - 1]] == "\\"}]
  101. }
  102.  
  103. # ◊◊◊◊ Electric indentation ◊◊◊◊ #
  104. proc bind::LeftBrace {} {
  105.     if {[isSelection]} { deleteSelection }
  106.     global electricBraces mode
  107.     if {!$electricBraces} {
  108.     insertText "\{"
  109.     return
  110.     }
  111.     mode::proc electricLeft
  112. }
  113.  
  114. proc ::electricLeft {} {
  115.     if {![catch {search -l [lineStart [pos::math [lineStart [getPos]] - 1]] \
  116.       -s -f 0 -r 0 "\}" [getPos]} res]} {
  117.     set end [getPos]
  118.     if {[pos::compare [getPos] != [maxPos]]} {
  119.         set end [pos::math $end + 1]
  120.     }
  121.     
  122.     if {[regexp -- "\}\[ \t\r\n\]*else" [getText [lindex $res 0] $end]]} {
  123.         set res2 [search -s -f 0 -r 1 {else} [getPos]]
  124.         oneSpace
  125.         set text [getText [lindex $res2 0] [getPos]]
  126.         if {[lookAt [pos::math [getPos] - 1]] != " "} {
  127.         append text " "
  128.         }
  129.         replaceText [pos::math [lindex $res 0] + 1] [getPos] " $text\{\r"
  130.         bind::IndentLine
  131.         return 
  132.     }
  133.     }
  134.     set pos [getPos]
  135.     set i [text::firstNonWsLinePos $pos]
  136.     
  137.     if {([pos::compare $i == $pos]) || ([lookAt [pos::math $pos - 1]] == " ")} {
  138.     insertText "\{\r" [text::indentString $pos] [text::Tab]
  139.     } else {
  140.     insertText " \{\r" [text::indentString $pos] [text::Tab]
  141.     }
  142. }
  143.  
  144. proc ::electricRight {} {
  145.     set pos [getPos]
  146.     set start [lineStart $pos]
  147.     
  148.     if {[catch {matchIt "\}" [pos::math $pos - 1]} matched]} {
  149.     beep
  150.     message "No matching '\{'!"
  151.     return
  152.     }
  153.     set text [getText [lineStart $matched] $matched]
  154.     regexp "^\[ \t\]*" $text indentation
  155.     if {[string trim [getText $start $pos]] != ""} {
  156.     insertText "\r" $indentation "\}\r" $indentation
  157.     blink $matched
  158.     return
  159.     }
  160.     set text "${indentation}\}\r$indentation"
  161.     replaceText $start $pos $text
  162.     goto [pos::math $start + [string length $text]]
  163.     blink [matchIt "\}" [pos::math $start - 2]]
  164. }
  165.  
  166. proc bind::RightBrace {} {
  167.     if {[isSelection]} { deleteSelection }
  168.     global electricBraces mode
  169.     if {!$electricBraces} {
  170.     insertText "\}"
  171.     catch {blink [matchIt "\}" [pos::math [getPos] - 2]]}
  172.     return
  173.     }
  174.     mode::proc electricRight
  175. }
  176.  
  177. proc bind::electricSemi {} {
  178.     global electricSemicolon
  179.     if {!$electricSemicolon} {
  180.     typeText ";"
  181.     return
  182.     }
  183.     mode::proc electricSemi
  184. }
  185.  
  186. proc ::electricSemi {} {
  187.     if {[isSelection]} { deleteSelection }
  188.     set pos [getPos]
  189.     set text [getText [lineStart $pos] $pos]
  190.     
  191.     set inFor 0
  192.     if {[string first "for" $text] != "-1"} {
  193.     set len [string length $text]
  194.     for {set i 0} {$i < $len} {incr i} {
  195.         switch -- [string index $text $i] {
  196.         "("    { incr inFor }
  197.         ")"    { incr inFor -1 }
  198.         }
  199.     }
  200.     }
  201.     
  202.     if {$inFor != 0 || [text::isInComment $pos]} {
  203.     insertText ";"
  204.     } else {
  205.     insertText ";\r" [text::indentString $pos]
  206.     }
  207. }
  208.  
  209. ## 
  210.  # -------------------------------------------------------------------------
  211.  #     
  212.  # "bind::CarriageReturn" --
  213.  #    
  214.  #    General    purpose    CR procedure.  Should be bound to 'return' for all 
  215.  #    modes really.  Calls a mode-specific procedure if required.
  216.  # -------------------------------------------------------------------------
  217.  ##
  218. proc bind::CarriageReturn {} {
  219.     if {[isSelection]} { deleteSelection }
  220.     global autoContinueComment
  221.     if {$autoContinueComment && ([text::isInComment [set p [getPos]] start])} {
  222.     # special case for beginning of line
  223.     if {[pos::compare $p == [lineStart $p]]} {
  224.         backwardChar
  225.     }
  226.     insertText "\r${start}"
  227.     return
  228.     }
  229.     mode::proc carriageReturn
  230. }
  231.  
  232. proc ::carriageReturn {} {
  233.     insertText "\r"
  234.     global indentOnReturn
  235.     if {$indentOnReturn} {bind::IndentLine}
  236. }
  237.  
  238. proc bind::IndentLine {} {
  239.     mode::proc indentLine
  240. }
  241.  
  242. proc insertActualTab {} { typeText "\t" }
  243.  
  244.  
  245.  
  246. ## 
  247.  # -------------------------------------------------------------------------
  248.  # 
  249.  # "text::isInComment" --
  250.  # 
  251.  # Are we in a block comment? Just checks if both the given line and the
  252.  # next line commence with any of a set of known block-comment characters.
  253.  # Not 100% satisfactory for C comments, but fine for all others.
  254.  # -------------------------------------------------------------------------
  255.  ##
  256. proc text::isInComment {pos {st ""}} {
  257.     if {[pos::compare $pos == [minPos]]} {
  258.     return 0
  259.     }
  260.     set p [lineStart $pos]
  261.     if {[pos::compare $pos == $p]} {
  262.     set pos [pos::math $pos - 1] ; set p [lineStart $pos]
  263.     }
  264.     set q [nextLineStart $pos]
  265.     set t [getText $p $q]
  266.     if { $st != "" } {
  267.     upvar $st a
  268.     }
  269.     if {![catch {comment::Characters "Paragraph"} cpar]} {
  270.     if {[regexp -- "^(.*)[quote::Regfind [string trim [lindex $cpar 0]]]" $t "" a]} {
  271.         if {![regexp -- "[quote::Regfind [string trim [lindex $cpar 1]]]" $t]} {
  272.         set len [string length [lindex $cpar 2]]
  273.         regsub -all "\[^ \t\]" $a " " a
  274.         append a [lindex $cpar 2]
  275.         return 1
  276.         }
  277.     }
  278.     }
  279.     # if the next line is a comment 
  280.     set qq [text::firstNonWsLinePos $q]
  281.     if {[pos::compare $qq == [maxPos]]} { 
  282.     return 0 
  283.     }
  284.     foreach commentCh [comment::Characters "General"] {    
  285.     if {[regexp -- "^\[ \t\]*[quote::Regfind ${commentCh}]\[ \t\]*" $t a]} {
  286.         # if we hit return in the middle of a line
  287.         if {[string trim [getText $pos $q]] != "" && [pos::compare $pos != $p]} { 
  288.         return 1
  289.         }
  290.         if {[getText $qq [pos::math $qq + [string length $commentCh]]] == $commentCh} {
  291.         return 1
  292.         }
  293.     }
  294.     }
  295.     return 0
  296. }
  297.  
  298.  
  299. # ◊◊◊◊ Indentation utility routines ◊◊◊◊ #
  300.  
  301. proc posX {pos} {return [lindex [posToRowCol $pos] 1] }
  302. # the above version doesn't work!
  303. if {[info tclversion] < 8.0} {
  304. proc posX {pos} {return [string length [text::maxSpaceForm [getText [lineStart $pos] $pos]]]}
  305. }
  306.  
  307. proc text::firstNonWs {pos} {
  308.     set p [text::firstNonWsPos $pos]
  309.     if {[pos::compare $p > [minPos]]} {
  310.     return [lookAt $p]
  311.     } else {
  312.     return ""
  313.     }
  314. }
  315.  
  316. ## 
  317.  # -------------------------------------------------------------------------
  318.  #   
  319.  # "text::firstNonWsPos" --
  320.  #  
  321.  #  This returns the position of the first non-whitespace character from
  322.  #  the start of pos' line.  It need not return something on the same
  323.  #  line.
  324.  # -------------------------------------------------------------------------
  325.  ##
  326. proc text::firstNonWsPos {pos} {
  327.     if {[catch {lindex [search -s -f 1 -r 1 "\[^ \t\r\n\]" [lineStart $pos]] 0} res]} {
  328.     return [lineStart $pos]
  329.     } else {
  330.     return $res
  331.     }
  332. }
  333.  
  334. proc text::firstNonWsLinePos {pos} {
  335.     if {[catch {lindex [search -s -f 1 -r 1 "\[^ \t\]" [lineStart $pos]] 0} res]} {
  336.     return [lineStart $pos]
  337.     } else {
  338.     return $res
  339.     }
  340. }
  341.  
  342. proc text::indentation {pos} {
  343.     return [search -s -m 0 -f 1 -r 1 "^\[ \t\]*\[^ \t\]" [lineStart $pos]]
  344. }
  345.  
  346. ## 
  347.  # -------------------------------------------------------------------------
  348.  # 
  349.  # "text::minSpaceForm" --
  350.  # 
  351.  #  Converts to minimal form: tabs then spaces.  Uses one regsub to do
  352.  #  the job.  Note that the regexp used relies upon the left-to-right
  353.  #  priority of branch matching.  If the regexp library used is more
  354.  #  sophisticated and finds maximal matches, then this is no good.
  355.  #  In that case use:
  356.  #        regsub -all $sp $ws "\t" ws
  357.  #        regsub -all " +\t" $ws "\t" ws
  358.  # -------------------------------------------------------------------------
  359.  ##
  360. if {[info tclversion] < 8.1} {
  361.     proc text::minSpaceForm {ws} {
  362.     regsub -all "([spacesEqualTab]| +\t)" $ws "\t" ws
  363.     return $ws
  364.     }
  365. } else {
  366.     proc text::minSpaceForm {ws} {
  367.     regsub -all [spacesEqualTab] $ws "\t" ws
  368.     regsub -all " +\t" $ws "\t" ws
  369.     return $ws
  370.     }
  371. }
  372.  
  373.  
  374. ## 
  375.  # -------------------------------------------------------------------------
  376.  # 
  377.  # "text::maxSpaceForm" --
  378.  # 
  379.  #  Converts it to maximal form - just spaces.
  380.  #  Just uses one funky regsub to do the job!  Takes account of tab-size,
  381.  #  spaces interspersed with tabs,...
  382.  # -------------------------------------------------------------------------
  383.  ##
  384. if {[info tclversion] < 8.1} {
  385.     proc text::maxSpaceForm {ws} {
  386.     set sp [spacesEqualTab]
  387.     regsub -all "(($sp)*) *\t" $ws "\\1$sp" ws
  388.     return $ws
  389.     }
  390. } else {
  391.     proc text::maxSpaceForm {ws} {
  392.     set sp [spacesEqualTab]
  393.     regsub -all $sp $ws "\t" ws
  394.     regsub -all " +\t" $ws "\t" ws
  395.     regsub -all "\t" $ws "$sp" ws
  396.     return $ws
  397.     }
  398. }
  399.  
  400.  
  401. ## 
  402.  # -------------------------------------------------------------------------
  403.  # 
  404.  # "spacesEqualTab" --
  405.  # 
  406.  #  Return the number of spaces equivalent to a single tab. If tabs are too
  407.  #  big, this won't work.
  408.  # -------------------------------------------------------------------------
  409.  ##
  410. proc spacesEqualTab {} {
  411.     getWinInfo a
  412.     string range "              " 1 $a(tabsize)
  413. }
  414.  
  415. proc doubleLookAt {pos} {return [getText $pos [pos::math $pos + 2]]}
  416.  
  417. set bind::_IndentSpaces "                                                   \
  418.                                          "
  419. set bind::_IndentTabs "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
  420.  
  421. proc text::indentOf {size} {
  422.     global bind::_IndentSpaces bind::_IndentTabs indentUsingSpacesOnly
  423.     if {$indentUsingSpacesOnly} {
  424.     return [string range ${bind::_IndentSpaces} 1 $size]
  425.     } else {
  426.     getWinInfo a
  427.     set ret [string range ${bind::_IndentTabs} 1 [expr {$size / $a(tabsize)}]]
  428.     append ret [string range ${bind::_IndentSpaces} 1 [expr {$size % $a(tabsize)}]]
  429.     }
  430.     return $ret
  431. }
  432.  
  433. # returns the indent string of the line named by 'pos'
  434. proc text::indentString {pos} {
  435.     set beg [lineStart $pos]
  436.     regexp "^\[ \t\]*" [getText $beg [nextLineStart $beg]] white
  437.     return $white
  438. }
  439.  
  440. # returns the indent string of the line up to position 'pos' 
  441. proc text::indentTo {pos} {
  442.     regexp "^\[ \t\]*" [getText [lineStart $pos] $pos] white
  443.     return $white
  444. }
  445.  
  446. ## 
  447.  # -------------------------------------------------------------------------
  448.  # 
  449.  # "text::indentBy" --
  450.  # 
  451.  #  Take the given block of text, and insert/remove spaces and tabs to
  452.  #  indent it $by spaces to the left or right. This version should work
  453.  #  ok for Tcl 7.5/8.0/8.1
  454.  # -------------------------------------------------------------------------
  455.  ##
  456. proc text::indentBy {text by} {
  457.     global bind::_IndentSpaces indentUsingSpacesOnly
  458.     set sp [spacesEqualTab]
  459.     # Convert all leading whitespace to spaces
  460.     while {[regsub -all "((^|\r|\n)($sp)*) *\t" $text "\\1$sp" text]} {}
  461.     set sby [string range ${bind::_IndentSpaces} 1 [expr {abs($by)}]]
  462.     if {$by < 0} {
  463.     # need to indent less
  464.     regsub -all "(^|\r|\n)$sby" $text "\\1" text
  465.     } else {
  466.     # need to indent more: add spaces to beginning of each line,
  467.     # apart from blank lines and the final line
  468.     regsub -all "\[\r\n\](\[^\r\n\])" $sby$text "\r$sby\\1" text
  469.     }
  470.     # We already converted everything to spaces, so we only convert
  471.     # to tabs if the user wants them.
  472.     if {!$indentUsingSpacesOnly} {
  473.     while {[regsub -all "((^|\r|\n)\t*)$sp" $text "\\1\t" text]} {}
  474.     }
  475.     return $text
  476. }
  477.  
  478. proc text::halfTab {} {
  479.     global indent_amounts
  480.     return [string range "              " 1 $indent_amounts(1)]
  481. }
  482. proc text::Tab {} {
  483.     global indentationAmount
  484.     return [text::indentOf $indentationAmount]
  485. }
  486.  
  487. proc text::getTabSize {} {
  488.     getWinInfo a
  489.     return $a(tabsize)
  490. }
  491.  
  492. # ◊◊◊◊ General purpose indentation ◊◊◊◊ #
  493.  
  494. proc indentSelection {} {
  495.     mode::proc indentRegion
  496. }
  497.  
  498. ## 
  499.  # -------------------------------------------------------------------------
  500.  # 
  501.  # "text::inCommentBlock" --
  502.  # 
  503.  #  Returns 'startpos endpos' if true, else returns an error.  Not 
  504.  #  particularly robust, but not too bad either
  505.  # -------------------------------------------------------------------------
  506.  ##
  507. proc text::inCommentBlock {pos} {
  508.     set chars [comment::Characters Paragraph]
  509.     set start [string trim [lindex $chars 0]]
  510.     set end [string trim [lindex $chars 1]]
  511.     if {$start == $end} {
  512.     error "No"
  513.     }
  514.     set cS [search -s -f 0 -r 0 -l [pos::math $pos - 1000] $start $pos]
  515.     set cE [search -s -f 1 -r 0 -l [pos::math $pos + 1000] $end [lindex $cS 1]]
  516.     if {[pos::compare $pos >= [lindex $cE 1]]} {    
  517.     error "No"
  518.     } else {
  519.     return [list [lindex $cS 0] [lindex $cE 1]]
  520.     }
  521. }
  522.  
  523.  
  524. # Tom's new regexp which I don't use now.  Shame.
  525. #set commentRegexp       {/\*[^*]*\*+([^/*][^*]*\*+)*/}
  526.  
  527. #########################################################################
  528. # Generic C-style indentation (works for Tcl and Perl)
  529. # Significant changes by Vince.
  530. proc ::indentLine {} {
  531.     global commentsArentSpecialWhenIndenting
  532.     # get details of current line
  533.     set beg [lineStart [getPos]]
  534.     set text [getText $beg [nextLineStart $beg]]
  535.     regexp "^\[ \t\]*" $text white
  536.     set len [string length $white]
  537.     set epos [pos::math $beg + $len]
  538.  
  539.     if {[pos::compare $beg != [minPos]]} {
  540.     # Find last previous non-comment line and get its leading whitespace
  541.     set pos $beg
  542.     while 1 {
  543.         if {[pos::compare $pos == [minPos]] || [catch {search -s -f 0 -r 1 -i 0 -m 0 "^\[ \t\]*\[^ \t\r\n\]" [pos::math $pos - 1]} lst]} {
  544.         # search failed at top of file
  545.         set line "#"
  546.         set lwhite 0
  547.         break
  548.         }
  549.         if {!$commentsArentSpecialWhenIndenting && \
  550.           ![catch {text::inCommentBlock [lindex $lst 0]} res]} {
  551.         set pos [lindex $res 0]
  552.         } else {
  553.         set line [getText [lindex $lst 0] [pos::math [nextLineStart [lindex $lst 0]] - 1]]
  554.         set lwhite [posX [pos::math [lindex $lst 1] - 1]]    
  555.         break
  556.         }
  557.     }
  558.     
  559.     regexp "(\[^ \t\])\[ \t\]*\$" $line "" nextC
  560.     global indentationAmount electricColon
  561.     if {($nextC == "\{")} {
  562.         incr lwhite $indentationAmount
  563.     } elseif {$nextC == ":" && $electricColon} {
  564.         incr lwhite [expr {$indentationAmount /2}]
  565.     }
  566.     
  567.     if {[regexp ":\[ \t\r\n\]*\$" $text] && $electricColon} {incr lwhite [expr {-$indentationAmount / 2}]}
  568.     if {[lookAt $epos] == "\}"} {
  569.         incr lwhite [expr {-$indentationAmount}]
  570.     }
  571.     } else {
  572.     set lwhite 0
  573.     }
  574.     set lwhite [text::indentOf $lwhite]
  575.     if {$white != $lwhite} {
  576.     replaceText $beg $epos $lwhite
  577.     }
  578.     goto [pos::math $beg + [string length $lwhite]]
  579. }
  580.  
  581.  
  582. proc ::indentRegion {} {
  583.     set from [lindex [posToRowCol [getPos]] 0]
  584.     set to [lindex [posToRowCol [selEnd]] 0]
  585.     select [getPos]
  586.     while {$from <= $to} {
  587.     goto [rowColToPos $from 0]
  588.     bind::IndentLine
  589.     incr from
  590.     }
  591. }
  592.